#region Copyright
//
// (C) Copyright 2011 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//
#endregion // Copyright

#region Namespaces
using System;
using System.Diagnostics;
using System.Collections.Generic;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.ExtensibleStorage;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
#endregion // Namespaces

namespace ExtensibleStorage
{
  [Transaction( TransactionMode.Manual )]
  public class Command : IExternalCommand
  {
    static string PluralSuffix( int n )
    {
      return 1 == n ? "" : "s";
    }

    /// <summary>
    /// Create a data structure, attach it to a wall, 
    /// populate it with data, and retrieve the data 
    /// back from the wall
    /// </summary>
    void StoreDataInWall(
      Wall wall,
      XYZ dataToStore )
    {
      Transaction createSchemaAndStoreData
        = new Transaction( wall.Document, "tCreateAndStore" );       

      createSchemaAndStoreData.Start();

      SchemaBuilder schemaBuilder = new SchemaBuilder(
        new Guid( "720080CB-DA99-40DC-9415-E53F280AA1F0" ) 
        );

      // Allow anyone to read the object

      schemaBuilder.SetReadAccessLevel(
        AccessLevel.Public 
        );

      // Restrict writing to this vendor only

      schemaBuilder.SetWriteAccessLevel(
        AccessLevel.Vendor 
        );

      // Required because of restricted write-access

      schemaBuilder.SetVendorId( "ADNP" );

      // Create a field to store an XYZ

      FieldBuilder fieldBuilder = schemaBuilder
        .AddSimpleField( "WireSpliceLocation",
        typeof( XYZ ) );

      fieldBuilder.SetUnitType( UnitType.UT_Length );

      fieldBuilder.SetDocumentation( "A stored "
        + "location value representing a wiring "
        + "splice in a wall." );

      schemaBuilder.SetSchemaName( "WireSpliceLocation" );

      // Register the schema

      Schema schema = schemaBuilder.Finish(); 

      // Create an entity (object) for this schema (class)

      Entity entity = new Entity( schema );

      // Get the field from the schema

      Field fieldSpliceLocation = schema.GetField(
        "WireSpliceLocation" 
        );

      // Set the value for this entity

      entity.Set<XYZ>( fieldSpliceLocation, dataToStore,
        DisplayUnitType.DUT_METERS );

      // Store the entity on the element

      wall.SetEntity( entity );

      createSchemaAndStoreData.Commit();

      // Read back the data from the wall

      Entity retrievedEntity = wall.GetEntity( schema );

      XYZ retrievedData = retrievedEntity.Get<XYZ>(
        schema.GetField( "WireSpliceLocation" ),
        DisplayUnitType.DUT_METERS 
        );
    }

    /// <summary>
    /// List all schemas in Revit memory across all documents.
    /// </summary>
    void ListSchemas()
    {
      IList<Schema> schemas = Schema.ListSchemas();

      int n = schemas.Count;

      Debug.Print( 
        string.Format( "{0} schema{1} defined:", 
        n, PluralSuffix( n ) ) 
        );

      foreach( Schema s in schemas )
      {
        IList<Field> fields = s.ListFields();

        n = fields.Count;

        Debug.Print( 
          string.Format( "Schema '{0}' has {1} field{2}:", 
            s.SchemaName, n, PluralSuffix( n ) ) 
            );

        foreach( Field f in fields )
        {
          Debug.Print(
            string.Format( 
              "Field '{0}' has value type {1} and unit type {2}",
              f.FieldName, f.ValueType, f.UnitType ) 
              );
        }
      }
    }

    class WallFilter : ISelectionFilter
    {
      public bool AllowElement( Element e )
      {
        return e is Wall;
      }

      public bool AllowReference( Reference r, XYZ p )
      {
        return true;
      }
    }

    public Result Execute(
      ExternalCommandData commandData,
      ref string message,
      ElementSet elements 
      )
    {
      UIApplication uiapp = commandData.Application;
      UIDocument uidoc = uiapp.ActiveUIDocument;
      Document doc = uidoc.Document;

      try
      {
        // Pick an element and define the XYZ 
        // data to store at the same time

        Reference r = uidoc.Selection.PickObject( 
          ObjectType.Face, 
          new WallFilter(), 
          "Please pick a wall at a point on one of its faces" 
          );

        Wall wall = doc.get_Element( r.ElementId ) as Wall;
        XYZ dataToStore = r.GlobalPoint;

        // Store the data, and also 
        // demonstrate reading it back

        StoreDataInWall( wall, dataToStore );

        // List all schemas in memory across all documents

        ListSchemas();

        return Result.Succeeded;
      }
      catch( Exception ex )
      {
        message = ex.Message;
        return Result.Failed;
      }
    }
  }
}
